package org.h2.index;
import java.util.ArrayList;  
import java.util.List;
import java.util.LinkedList;
import java.util.Set;
import java.util.HashSet;
import org.h2.engine.Session; 
import org.h2.message.DbException;
import org.h2.result.Row;
import org.h2.result.RowList;
import org.h2.result.SearchRow;
import org.h2.table.Column;
import org.h2.table.IndexColumn;
import org.h2.table.RegularTable;
import org.h2.value.Value;
import org.h2.value.ValueNull;
import org.h2.table.Column;

public class MultiIndexIndex extends BaseIndex {	
	private Set<Index> _indexes;
	
    public MultiIndexIndex(RegularTable table, int id, String indexName, IndexColumn[] columns, IndexType indexType) {
        initBaseIndex(table, id, indexName, columns, indexType);
		_indexes = new HashSet<Index>();
        reset();
    }

    private void reset() {

    }

    public void truncate(Session session) {
        reset();
    }

    public void add(Session session, Row row) {
		//When the add is called for the other indexes, the rows will be added to it
		//so we dont do it here
    }

    public void remove(Session session, Row row) {
		//when remove is called for the other indexes the rows will be removed
    }

	private List<Integer> getUsedColumnIds(SearchRow row) {
		LinkedList<Integer> used = new LinkedList<Integer>();
		for (int i = 0; i < row.getColumnCount(); ++i) {
			Value v = row.getValue(i);
			//if the column's value is null, it's not being searched on
			if (v != ValueNull.INSTANCE) {
				used.add(i);
			}
		}
		
		return used;
	}
	
	/**
	Tim: This logic is still kind of muddled. It works to limit any idnexes that do not work on
		the columns we are searching for. It can be improved, I am certain of it
	
	*/
	private Index[] getUsableIndexes(SearchRow row) {
		Set<Index> usable = new HashSet<Index>();
		
		List<Integer> usedColumns = getUsedColumnIds(row);
		for (Index index : _indexes) {
			for (int i = 0; i < usedColumns.size(); ++i) {
				boolean used = false;
				for (Column c : index.getColumns()) {
					//does the index work with this column?
					if (c.getColumnId() == usedColumns.get(i)) {					
						usable.add(index);
						used = true;
						break;
					}
				}
				
				//move on to the next index
				if (used) {	
					//no longer care about this column
					usedColumns.remove(i);
					break;
				}
			}
		}
		
		System.out.println("USABLE INDEXES:" + usable.size());
		return usable.toArray(new Index[usable.size()]);
	}
	
    public Cursor find(Session session, SearchRow first, SearchRow last) {
		System.out.println("Beginning find on MultiIndexIndex");
		//Thread.dumpStack();
		if (first != null) {
			System.out.println("SearchRow First:" + first);
		} else {
			System.out.println("FIRST WAS NULL");
		}
		
		if (last != null) {
			System.out.println("SearchRow Last:" + last);
		} else {
			System.out.println("SECOND WAS NULL");
		}
		
		ArrayList<Cursor> cursors = new ArrayList<Cursor>();
		//we only care about the indexes that are usable for the columns we need
		for (Index i : getUsableIndexes(first)) {
			Cursor c = i.find(session, first, last);
			if (c != null) {
				System.out.println("Cursor added.");
				cursors.add(c);
			}
		}
	
		System.out.println("Cursors:" + cursors.size());
		//for AND we union the results from all the queries
		Set<Row> set = new HashSet<Row>();
		for (Cursor c : cursors) {
			System.out.println("Cursor for:" + c.getClass().toString());
			while (c.next()) {
				System.out.println("\tRow:" + c.get());
				set.add(c.get());
			}
		}

        RowList rl = new RowList(session);
		for (Row r : set) {
			rl.add(r);
		}
		System.out.println("RowList:" + set.size());
		return new MultiIndexCursor(rl);
    }

    public long getRowCount(Session session) {
        return getRowCountApproximation();
    }

    public long getRowCountApproximation() {
        return 1;//_indexes.size() <= 0 ? _indexes.iterator().next().getRowCountApproximation() : 0;
    }

    public long getDiskSpaceUsed() {
        return 0;
    }

    public void close(Session session) {
        // nothing to do
    }

    public void remove(Session session) {
        // nothing to do
    }

    public double getCost(Session session, int[] masks) {
        for (Column column : columns) {
            int index = column.getColumnId();
            int mask = masks[index];
            if ((mask & IndexCondition.EQUALITY) != IndexCondition.EQUALITY) {
                return Long.MAX_VALUE;
            }
        }
        return 2;
    }

    public void checkRename() {
        // ok
    }

    public boolean needRebuild() {
        return true;
    }

    public boolean canGetFirstOrLast() {
        return false;
    }

    public Cursor findFirstOrLast(Session session, boolean first) {
        throw DbException.getUnsupportedException("HASH");
    }

    public boolean canScan() {
        return false;
    }
	
	private void rebuildIndexColumns() {
		Set<IndexColumn> cols = new HashSet<IndexColumn>();
		for (Index i : _indexes) {
			for (int j = 0; j < i.getIndexColumns().length; ++j) {
				cols.add(i.getIndexColumns()[j]);
			}
		}
		
		//build the new array
		indexColumns = new IndexColumn[cols.size()];
		columnIds = new int[cols.size()];
		columns = new Column[cols.size()];
		int i = 0;
		for (IndexColumn c : cols) {
			Column col = c.column;
			columnIds[i] = col.getColumnId();
			columns[i] = col;
			indexColumns[i++] = c;
		}

		System.out.println("New columns are:");
		for (i = 0; i < indexColumns.length; ++i) {
			System.out.print("\t" + indexColumns[i].column.getName() + " :: " + columnIds[i]);
		}
		System.out.println();
	}
	
	public void addIndex(Index index) {
		if (_indexes.add(index)) {
			System.out.println("Added index. Rebuilding columnns");
			rebuildIndexColumns();
		}
	}
	
	public void removeIndex(Index index) {
		if (_indexes.remove(index)) {
			System.out.println("Removed index. Rebuilding columns");
			rebuildIndexColumns();
		}
	}
}